package com.thiswhyme.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.thiswhyme.redis.geo.GeoTemplate;
import com.thiswhyme.redis.geo.RedisDataGeoTemplate;
import com.thiswhyme.redis.geo.RedissonGeoTemplate;
import com.thiswhyme.redis.lock.DistributedLockAspect;
import com.thiswhyme.redis.subscribe.DefaultRedisPublisher;
import com.thiswhyme.redis.subscribe.RedisSubscriber;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.codec.JsonJacksonCodec;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * Created by thiswhyme on 2020/7/2.
 */
@Slf4j
@Configuration
@AutoConfigureBefore(RedisAutoConfiguration.class)
@EnableConfigurationProperties(BizRedisProperties.class)
public class BizRedisAutoConfiguration {

    @Autowired(required = false)
    private List<RedisSubscriber> subscribers;

    /**
     * redisTemplate配置
     * @param connectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域(field,get,set)，访问修饰符(public,private,protected)
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * redissonClient配置
     * @param properties
     * @return
     */
    @Bean
    @ConditionalOnProperty(value = "thiswhyme.redis.enable-redisson", havingValue = "true")
    public RedissonClient redissonClient(RedisProperties properties) {
        Config config = new Config();
        if(null != properties.getSentinel()) {
            SentinelServersConfig serversConfig = config.useSentinelServers()
                    .setMasterName(properties.getSentinel().getMaster())
                    .setPassword(properties.getPassword())
                    .setDatabase(properties.getDatabase());
            properties.getSentinel().getNodes().forEach(node -> {
                serversConfig.addSentinelAddress( "redis://" + node);
            });
        } else if(null != properties.getCluster()) {
            ClusterServersConfig serversConfig = config.useClusterServers()
                    .setPassword(properties.getPassword())
                    .setScanInterval(5000);
            properties.getCluster().getNodes().forEach(node -> {
                serversConfig.addNodeAddress( "redis://" + node);
            });
        } else if(null != properties.getHost()) {
            config.useSingleServer()
                    .setAddress("redis://" + properties.getHost() + ":" + properties.getPort())
                    .setPassword(properties.getPassword())
                    .setDatabase(properties.getDatabase());
        } else {
            return null;
        }
        config.setCodec(new JsonJacksonCodec());
        return Redisson.create(config);
    }

    @Bean
    @ConditionalOnBean(RedissonClient.class)
    public GeoTemplate redissonGeoTemplate(RedissonClient redissonClient) {
        return new RedissonGeoTemplate(redissonClient);
    }

    @Bean
    @ConditionalOnMissingBean({GeoTemplate.class, RedissonClient.class})
    public GeoTemplate redisGeoTemplate(RedisTemplate redisTemplate) {
        return new RedisDataGeoTemplate(redisTemplate);
    }

    @Bean
    @ConditionalOnBean(RedissonClient.class)
    public DistributedLockAspect distributedLockAspect(RedissonClient redissonClient) {
        return new DistributedLockAspect(redissonClient);
    }

    /**
     * 默认redis消息发送器
     * @param redisTemplate
     * @return
     */
    @Bean
    @ConditionalOnProperty(value = "thiswhyme.redis.enable-subscribe", havingValue = "true")
    public DefaultRedisPublisher defaultRedisPublisher(StringRedisTemplate redisTemplate) {
        return new DefaultRedisPublisher(redisTemplate);
    }

    /**
     * 将subscriber放入消息监听容器
     * @param connectionFactory
     * @return
     */
    @Bean
    @ConditionalOnBean(RedisSubscriber.class)
    @ConditionalOnProperty(value = "thiswhyme.redis.enable-subscribe", havingValue = "true")
    public RedisMessageListenerContainer container(LettuceConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        if (this.subscribers != null && this.subscribers.size() > 0) {
            for (RedisSubscriber subscriber : this.subscribers) {
                if (subscriber == null || !StringUtils.hasText(subscriber.getChannelName())) {
                    continue;
                }
                container.addMessageListener(subscriber, new PatternTopic(subscriber.getChannelName()));
            }
        }
        return container;
    }
}
